//是否作为DOM Properties 来设置属性 
//  如：el.checked = true  
// 而不是el.setAttribute('checked', true)
function shouldSetAsProps (el, key, value) {
    // 特殊处理， 遇到则补全
    if (key === 'form' && el.tagName === 'INPUT') {
        return false;
    }

    return key in el;
}


// 全局变量
let currentInstance = null; // 当前组件实例
// 设置当前实例方法
function setCurrentInstance (instance) {
    currentInstance = instance
}

// 其他的周期函数钩子 类似写法
function onMounted (fn) {
    if (currentInstance) {
        currentInstance.mounted.push(fn)
    } else {
        console.log('onMounted 函数只能在 setup中调用')
    }
}

// 创建渲染器
function createRenderer (options) {

    //通过options 拿到对应操作的api
    const {
        createElement,
        setElementText,
        insert,
        patchProps,
        createText,
        setText,
        createComment,
        setComment,

    } = options

    // 通过key 复用元素 且移动位置
    /**
   * @param {旧vnode的子节点} oldChildren
   * @param {新vnode的子节点} newChildren
   * @param {容器} container
   */
    function simpleDiff2 (oldChildren, newChildren, container) {
        //新children中 当前子节点 最大索引值
        let lastIndex = 0;
        for (let i = 0; i < newChildren.length; i++) {
            const newChild = newChildren[i];

            // 是否是新元素 没有在旧children中找到
            let find = false;
            for (let j = 0; j < oldChildren.length; j++) {
                const oldChild = oldChildren[j];
                if (newChild.key === oldChild.key) {
                    //找到可复用元素
                    find = true;

                    // 如果当前找到的节点在旧children中的索引小于最大索引值 则需要移动位置
                    if (j < lastIndex) {
                        // 移动位置
                        const prevVnode = newChildren[i - 1];
                        // 如果不是第一个元素
                        if (prevVnode) {
                            // 找到上一个元素的 下一个兄弟元素
                            const anchor = prevVnode.el.nextSibling;

                            // 插入到anchor前面
                            insert(newChild.el, container, anchor);
                        }


                    } else {
                        //如果大于最大索引值 则 更新最大索引值
                        lastIndex = j;
                    }

                    break; // 找到后退出内层循环
                }
            }

            // 如果没有找到 则说明是新元素
            if (!find) {
                // 找到上一个元素的 下一个兄弟元素
                const prevVnode = newChildren[i - 1];
                // 如果不是第一个元素
                let achor;
                if (prevVnode) {
                    // 找到上一个元素的 下一个兄弟元素
                    anchor = prevVnode.el.nextSibling;

                } else {
                    anchor = container.firstChild;
                }

                // 挂载新元素
                patch(null, newChild, container, anchor);
            }
        }

        // 卸载多余的旧元素
        for (let i = 0; i < oldChildren.length; i++) {
            const oldChild = oldChildren[i];
            //看旧元素是否在新的children中
            const find = newChildren.find(child => child.key === oldChild.key);

            //如果不在则卸载
            if (!find) {
                unmount(oldChild);
            }
        }

    }



    //通过长度去更新;没有移动元素逻辑
    /**
     * @param {旧vnode的子节点} oldChildren
     * @param {新vnode的子节点} newChildren
     * @param {容器} container
     */
    function simpleDiff1 (oldChildren, newChildren, container) {
        const oldLen = n1.children.lenght;
        const newLen = n2.children.lenght;
        const minLen = Math.min(oldLen, newLen);


        for (let i = 0; i < minLen.lenght; i++) {
            patch(oldChildren[i], newChildren[i], container)
        }

        if (oldLen > newLen) {
            for (let i = minLen; i < oldLen; i++) {
                unmount(oldChildren[i]);
            }
        } else {
            for (let i = minLen; i < newLen; i++) {
                patch(null, newChildren[i], container);
            }
        }

    }


    // 双端diff 算法
    /**
     * @param {旧vnode} n1
     * @param {新vnode} n2
     * @param {容器} container
     */
    function patchChildren (n1, n2, container) {
        const oldChildren = n1.children;
        const newChildren = n2.children;

        // 四个索引 
        let oldStartIdx = 0; // 旧children 开始索引
        let oldEndIdx = oldChildren.length - 1; // 旧children 结束索引
        let newStartIdx = 0; // 新children 开始索引
        let newEndIdx = newChildren.length - 1; // 新children 结束索引

        // 对应四个节点
        let oldStartVnode = oldChildren[oldStartIdx]; // 旧children 开始节点
        let oldEndVnode = oldChildren[oldEndIdx]; // 旧children 结束节点
        let newStartVnode = newChildren[newStartIdx]; // 新children 开始节点
        let newEndVnode = newChildren[newEndIdx]; // 新children 结束节点

        // 循环比较
        while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
            // 旧 头部节点 和旧尾部节点不存在 说明也被处理了 ，则 跳过
            if (!oldStartVnode.el) {
                oldStartVnode = oldChildren[++oldStartIdx];
            } else if (!oldEndVnode.el) {
                oldEndVnode = oldChildren[--oldEndIdx];
            } else if (oldStartVnode.key === newStartVnode.key) {
                // 直接打补丁更新 不需要挪动
                patch(oldStartVnode, newStartVnode, container);

                // 更新 新旧开始节点
                oldStartVnode = oldChildren[++oldStartIdx];
                newStartVnode = newChildren[++newStartIdx];

            } else if (oldEndVnode.key === newEndVnode.key) {
                // 直接打补丁更新 不需要挪动
                patch(oldEndVnode, newEndVnode, container);

                //更新 新旧结束节点
                oldEndVnode = oldChildren[--oldEndIdx];
                newEndVnode = newChildren[--newEndIdx];

            } else if (oldStartVnode.key === newEndVnode.key) {
                // 先打补丁后挪动，将旧开始节点挪到 旧结束的下一个兄弟元素的前面
                patch(oldStartVnode, newEndVnode, container);

                const anchor = oldEndVnode.el.nextSibling;
                insert(oldStartVnode.el, container, anchor);

                // 更新 旧开始节点 新结束节点
                oldStartVnode = oldChildren[++oldStartIdx];
                newEndVnode = newChildren[--newEndIdx];

            } else if (oldEndVnode.key === newStartVnode.key) {
                // 先打补丁后挪东， 将旧结束节点 挪到 旧开始节点的前面
                patch(oldEndVnode, newStartVnode, container);

                const anchor = oldStartVnode.el;
                insert(oldEndVnode.el, container, anchor);

                // 更新 旧结束节点 新开始节点
                oldEndVnode = oldChildren[--oldEndIdx];
                newStartVnode = newChildren[++newStartIdx];
            } else {
                // 当四个节点都不能匹配时， 则需要在旧节点中查找新开始节点的key
                const idxInOld = oldChildren.findIndex(child => child.key === newStartVnode.key);
                if (idxInOld > 0) { //找到了就往旧头部节点前插入
                    const oldVnodeToMove = oldChildren[idxInOld]; // 要移动的旧节点
                    // 先打补丁
                    patch(oldVnodeToMove, newStartVnode, container);
                    // 获取对应锚点
                    const anchor = oldStartVnode.el;
                    // 移动元素
                    insert(oldVnodeToMove.el, container, anchor);
                    oldChildren[idxInOld] = undefined;
                } else { //没有找到说明时新节点， 
                    patch(null, newStartVnode, container, oldStartVnode.el);
                }
                newStartVnode = newChildren[++newStartIdx];
            }
        }

        // 看是否有新节点遗漏
        if (newStartIdx <= newEndIdx && oldStartIdx > oldEndIdx) {
            // 说明新节点有剩余， 则将新节点插入到旧开始节点的前面
            for (let i = newStartIdx; i <= newEndIdx; i++) {
                patch(null, newChildren[i], container, oldStartVnode.el);
            }
        } else if (newStartIdx > newEndIdx && oldStartIdx <= oldEndIdx) {// 旧节点有剩余 则卸载
            for (let i = oldStartIdx; i <= oldEndIdx; i++) {
                unmount(oldChildren[i]);
            }

        }
    }

    // 任务缓存队列  使用set 可自动去重
    const queue = new Set();

    //表示 是否正在刷新任务队列
    let isFlushing = false;
    // 创建一个立即resolve 的 promise实例， 用于异步更新
    const p = Promise.resolve();

    // 挂载组件的调度器主要函数 ，用来将一个任务添加到缓存队列中，并开始刷新队列
    function queueJob (job) {
        // 将任务添加到缓存队列中
        queue.add(job);
        // 如果没有正在刷新队列，则开始刷新队列
        if (!isFlushing) {
            // 开始刷新队列
            isFlushing = true;
            // 异步刷新队列
            p.then(() => {
                try {
                    // 循环刷新队列中的任务
                    queue.forEach(job => job());
                } finally {
                    // 重置状态
                    isFlushing = false;
                    queue.clear = 0;
                }
            })
        }
    }

    // 解析props 和 $attrs
    /**
     * @param {自身的props选项} options
     * @param {传入的props} propsData
     */
    function resolveProps (options, propsData) {
        const props = {};
        const attrs = {};

        // 遍历propsData
        for (const key in propsData) {
            // 如果自身定义过 相关的key 就是props的内容，
            // 否则就是透传的内容 即$attrs
            if (key in options || key.startsWith('on')) {
                props[key] = propsData[key];
            } else {
                attrs[key] = propsData[key];
            }
        }

        return [props, attrs];
    }


    /**
     * @param {旧vnode} vnode
     * @param {容器} container
     * @param {锚点元素} achor
     * 
     */
    function mountComponent (vnode, contiainer, achor) {
        // 通过vnode 获取组件的选项对象， 即vnode.type
        const componentOptions = vnode.type;
        // 获取组件的渲染函数
        const { render, data, props: propsOptions, setup,
            beforeCreate, created, beforeMount, mounted,
            beforeUpdate, updated } = componentOptions;

        // 先调用beforeCreate 钩子
        beforeCreate && beforeCreate();

        // 调用data 获取原始数据 且变成响应式
        const state = reactive(data())

        // 调用resolveProps函数 解析props 和$attrs 属性
        const [props, attrs] = resolveProps(propsOptions, vnode.props);

        // 使用编辑好的 vnode.children 对象 作为slots 对象
        const slots = vnode.children || {}

        // 定义组件实例， 一个组件实例本质上就是一个对象， 包含了与组件相关的状态信息
        const instance = {
            state,
            props: shallowReactive(props), // 浅响应式
            // 表示是否已挂载
            isMounted: false,
            // 组件所需渲染的内容
            subTree: null,
            //  将插槽添加到组件实例上 
            slots,
            // 用于存储通过onMounted 函数注册的生命周期钩子函数
            mounted: []
        }

        function emit (event, ...payload) {
            // change --> onChange 
            const eventName = `on${event[0].toUpperCase()}${event.slice(1)}`;
            // 获取对应事件
            const handler = instance.props[eventName];
            if (handler) {
                handler(...payload);
            } else {
                console.error(`不存在该事件：${eventName}`);
            }
        }


        //setupContext 是setup函数里的第二个参数,
        const setupContext = {
            attrs,
            emit,
            slots,
        }

        // 在调用setup函数之前获取当前组件实例
        setCurrentInstance(instance);

        // 调用setup 函数 将只读的props 和setupContext 作为参数传入，
        const setupResult = setup(shallowReadonly(instance.props), setupContext);

        // 在setup 函数执行完后 重置当前组件实例。
        setCurrentInstance(null);

        // setupState 用来存 setup函数返回的数据
        let setupState = null;

        if (typeof setupResult === 'function') {
            // 如果setup函数返回的是一个函数， 则报错 
            if (render) {
                console.error('setup 函数返回的是一个函数， 请检查！！！')
            }
            // 则将返回的函数作为render函数
            render = setupResult;
        } else {
            // 如果setup函数返回的是一个对象， 则将返回的对象作为setupState
            setupState = setupResult;
        }


        // 组件实例
        vnode.component = instance;


        // 创建 渲染上下文对象， 本质是组件实例的代理对象，
        // 在vue2中相当于this
        const renderContext = new Proxy(instance, {
            get (target, key, receiver) {
                const { state, props, slots } = target;

                if (key === '$slots') {
                    return slots;
                } else if (key in state) {
                    return state[key]
                } else if (key in props) {
                    return props[key]
                } else if (setupState && key in setupState) {
                    return setupState[key]
                } else {
                    console.error(`不存在该属性：${key}`)
                }
            },

            set (target, key, value, receiver) {
                const { state, props } = target;
                if (key in state) {
                    state[key] = value
                } else if (key in props) {
                    console.error(`props 是只读的，不能修改：${key}`)
                } else if (setupState && key in setupState) {
                    setupState[key] = value;
                } else {
                    console.error(`不存在该属性：${key}`)
                }
            }
        })



        // 调用created 钩子
        /**
         * Tips:
         * 在这个阶段就可以访问当前组件实例了！！！ 
         * 但是！ 访问不到其他组件的实列
         */
        created && created.call(renderContext);

        // 当组件自身状态发生改变时， 组件自动更新。
        effect(() => {
            // 执行渲染函数， 获取组件的渲染内容， 即render 函数返回的虚拟dom
            // --调用render的时候， 将this指向 state,这样render函数内容就可以访问state中的数据了
            const subTree = render.call(renderContext, renderContext);

            if (!instance.isMounted) {
                // 调用beforeMount 钩子
                beforeMount && beforeMount.call(renderContext);

                // 表示未挂载过
                patch(null, subTree, container, achor);
                instance.isMounted = true;

                // 调用mounted 钩子
                /**
                 * Tips:
                 * 在这个阶段就可以访问其他组件的实列了！！！
                 */
                instance.mounted && instance.mounted.forEach(hook => hook.call(renderContext));
            } else {
                // 调用beforeUpdate 钩子
                beforeUpdate && beforeUpdate.call(renderContext);

                // 表示已经挂载过了， 则需要更新
                // 新子树和旧子树进行打补丁
                patch(instance.subTree, subTree, container, achor);

                // 调用updated 钩子
                updated && updated.call(renderContext);
            }

            instance.subTree = subTree;

        }, {
            // 调度器  即组件更新时， 调用的函数
            scheduler: queueJob,
        })
    }


    // 判断props是否发生变化
    /**
     * @param {旧props} prevProps
     * @param {新props} nextProps
     */
    function hasPropsChanged (prevProps, nextProps) {
        const nextKeys = Object.keys(nextProps);
        // 如果长度变化了，则说明props发生变化
        if (nextKeys.length !== Object.keys(prevProps).length) {
            return true;
        }

        for (let i = 0; i < nextKeys.length; i++) {
            const key = nextKeys[i];
            // 如果对应key的值发生变化，则说明props发生变化
            if (nextProps[key] !== prevProps[key]) {
                return true;
            }
        }

        return false;
    }


    /**
     * @param {旧vnode} n1
     * @param {新vnode} n2
     * @param {容器} container
     */
    function patchComponent (n1, n2, container) {
        // 新节点 继承 就旧节点的实例
        const instance = (n2.component = n1.component);

        // 获取当前实例的props 
        const { props } = instance

        // 检查 子组件传递的props 是否发生变化，若是则更新props
        if (hasPropsChanged(n1.props, n2.props)) {
            const [nextProps] = resolveProps(n2.type.props, n2.props)

            for (const key in nextProps) {
                props[key] = nextProps[key]
            }

            // 删除多余的props
            for (const key in props) {
                if (!(key in nextProps)) {
                    delete props[key]
                }
            }

        }

    }


    /**
     * @param {旧vnode} n1
     * @param {新vnode} n2
     * @param {容器} container
     */
    function patchChildren (n1, n2, container) {
        //如果新节点是字符串
        if (typeof n2.children === 'string') {
            // 如果旧节点是数组  则需要先卸载
            if (Array.isArray(n1.children)) {
                n1.children.forEach(child => {
                    unmount(child);
                })
            }

            // 无论旧节点是 字符串或null直接设置文本覆盖即可，无需额外处理
            setElementText(container, n2.children)
        } else if (Array.isArray(n2.children)) {// 如果新节点有多个子节点


            // 旧节点的子节点是否为数组
            if (Array.isArray(n1.children)) {
                //双端diff算法处理。


            } else {
                // 无论是旧节点的子节点是 字符串还是null,先清空容器 再遍历打补丁
                setElementText(container, '');
                n2.children.forEach(child => {
                    patch(null, child, container);
                })
            }
        } else {
            if (Array.isArray(n1.children)) {
                n1.children.forEach(child => {
                    unmount(child);
                })
            } else {
                setElementText(container, '');
            }
        }
    }

    /**
      * @param {旧vnode} n1
      * @param {新vnode} n2 
      * 
      */
    function patchElement (n1, n2) {
        // 复用旧的元素
        const el = n2.el = n1.el;
        const oldProps = n1.props;
        const newProps = n2.props;

        //1. 更新props 
        for (const key in newProps) {
            if (newProps[key] !== oldProps[key]) {
                patchProps(el, key, oldProps[key], newProps[key])
            }
        }
        for (const key in oldProps) {
            if (!(key in newProps)) {
                // 如果新的props 中不存在这个属性  则删除这个属性
                patchProps(el, key, oldProps[key], null)
            }
        }

        // 2. 更新children
        pathChildren(n1, n2, el)
    }



    // 卸载元素
    function unmount (vnode) {
        if (vnode.type === Fragment) {
            vnode.children.forEach(child => {
                unmount(child);
            })
            return;
        }

        const parente = vnodel.el.parentNode;
        if (parente) {
            parente.removeChild(vnode.el)
        }
    }


    // 挂载元素
    function mountElement (vnode, container, anchor) {
        // 创建type类型元素
        const el = vnode.el = createElement(vnode.type);

        // 处理子节点 如果是string  表示元素有文本节点
        if (typeof vnode.children === 'string') {
            // 设置文本节点
            setElementText(el, vnode.children);
        } else if (Array.isArray(vnode.children)) {
            vnode.children.forEach(child => {
                //因为目前只是 挂载阶段 所以 旧vnode 传null即可
                patch(null, child, el)
            });
        }

        // 如果存在属性
        if (vnode.props) {
            for (const key in vnode.props) {
                // 调用setAttribute 方法设置数据
                const value = vnode.props[key]

                // 调用patchProps 方法设置属性
                patchProps(el, key, null, value);
            }
        }


        // 将元素添加到容器中
        insert(el, container, anchor);
    }

    //文本节点的标识
    const Text = Symbol();
    //注释节点的标识
    const Comment = Symbol();
    //  片段节点的标识
    const Fragment = Symbol();

    /**
     * n1 @param {*} 旧vnode
     * n2 @param {*} 新vnode
     * container @param {*} 容器
     *  在这里编写渲染逻辑
     */
    function patch (n1, n2, container, anchor) {

        // 区分新旧节点是否同一类型
        if (n1 && n1.type !== n2.type) {
            // 不是同一类型  就直接卸载旧的 
            unmount(n1);
            n1 = null;
        }


        const { type } = n2

        // 如果是字符串则表明是普通标签
        if (typeof type === 'string') {
            // 如果旧vnode 不存在 就表示挂载
            if (!n1) {
                // 挂载
                mountElement(n2, container, anchor)
            } else {
                // 打补丁  暂时省略
            }
        } else if (type === Text) {
            // 如果是文本节点
            if (!n1) {
                const el = n2.el = createText(n2.children);
                insert(el, container);
            } else {
                const el = n2.el = n1.el;
                if (n2.children !== n1.children) {
                    setText(el, n2.children);
                }
            }

        } else if (type === Comment) {
            // 如果是注释节点
            if (!n1) {
                const el = n2.el = createComment(n2.children);
                insert(el, container);
            } else {
                const el = n2.el = n1.el;
                if (n2.children !== n1.children) {
                    setComment(el, n2.children);
                }
            }
        } else if (type === Fragment) {
            // 如果是片段节点
            if (!n1) {
                // 如果就节点不存在，则直接遍历挂载fragment的子节点
                n2.children.forEach(child => {
                    patch(null, child, container)
                })
            } else {//如果是旧节点
                // 就只需要比较Fragment上的子节点即可，
                // 因为Fragment 没有真实的DOM  所以 直接复用旧节点的子节点
                patchChildren(n1, n2, container)
            }
        } else if (typeof type === 'object') {
            // 如果是对象 则它描述是组件

            // vnode.type 的值是选项对象， 作为组件来处理
            if (!n1) {//旧节点不存在则 挂载
                mountComponent(n2, container, anchor);

            } else {//旧节点存在则 打补丁
                // 组件的更新逻辑
                patchComponent(n1, n2, anchor);
            }

        } else if (typeof type === 'xxx') {
            // 其他类型 vnode  特殊处理
        }

    }



    function render (vnode, container) {
        if (vnode) {
            // 新 节点存在， 就将旧vnode 一起传给patch 函数 进行打补丁
            // container._vnode 表示旧vnode
            patch(container._vnode, vnode, container)
        } else {
            if (container._vnode) {
                // 只有旧vnode 存在，  没有新vnode   就是卸载操作
                // container.innerHTML = '';
                unmount(container._vnode);
            }
        }


        // 存vnode 作为下一个的旧vnode
        container._vnode = vnode;
    }

    // 服务端渲染内容时讲解
    function hydrate (vnode, container) {
    }

    return {
        render,
        hydrate
    }
}



const renderer = createRenderer({
    createElement (tag) {
        return document.createElement(tag);
    },
    setElementText (el, text) {
        el.textContent = text;
    },
    insert (el, parent, anchor = null) {
        parent.insertBefore(el, anchor);
    },
    createText (text) {
        return document.createTextNode(text);
    },
    setText (el, text) {
        el.nodeValue = text;
    },

    creatComment (text) {
        return document.createComment(text);
    },
    setComment (el, text) {
        el.nodeValue = text;
    },

    patchProps (el, key, prevValue, nextValue) {
        if (/^on/.test(key)) {
            // 定义 el._vei 为一个对象，存在事件名称到事件处理函数的映射
            const invokers = el._vei || (el._vei = {});

            //获取为该元素伪造的事件处理函数 invoker
            let invoker = invokers[key];
            // 获取事件名称  如：onClick  => click
            const name = key.slice(2).toLowerCase();

            if (nextValue) {
                if (!invoker) {
                    // 第一次绑定事件
                    // 为元素创建一个伪造的事件处理函数 invoker
                    //vei  vue event invoker
                    // 将事件处理函数映射到对应的key下，避免覆盖。
                    invoker = el._vei[key] = (e) => {

                        // e.timeStamp 事件发生的时间
                        // 如果事件发生的时间小于事件处理函数绑定的时间 则直接return
                        //--------- 此处是为了屏蔽绑定事件晚于事件发生事件的事件处理函数的 执行-----
                        if (e.timeStamp < invoker.attached) return;

                        // 当伪造事件执行的时候  会执行真正的事件处理函数 
                        // 如果是个数组 则遍历执行  否则 直接执行
                        if (Array.isArray(invoker.value)) {
                            invoker.value.forEach(fn => fn(e))
                        } else {
                            invoker.value(e)
                        }
                    }

                    //将真正的事件处理函数赋值给 invoker.value
                    invoker.value = nextValue;

                    // 记录时间绑定的时间
                    invoker.attached = performance.now();

                    // 新绑定事件
                    el.addEventListener(name, invoker);
                } else {
                    // 如果invoker 存在 则表示 更新 事件
                    // 只需要更新 invoker.value 即可
                    invoker.value = nextValue;
                }
            } else if (invoker) {
                //新事件不存在 且之前绑定的事件存在 则 移除事件
                el.removeEventListener(name, invoker.value);
            }

        } else if (key === 'class') {
            el.className = nextValue || '';
        } else if (shouldSetAsProps(el, key, nextValue)) {
            const type = typeof el[key]
            if (type === 'boolean' && nextValue === '') {
                el[key] = true;
            } else {
                el[key] = nextValue;
            }
        } else {
            el.setAttribute(key, nextValue);
        }
    }
})

// 组件
const MyComponent = {
    // 组件名称 可选值
    name: 'MyComponent',
    // 接受props内容
    props: {
        title: String,
    },

    //用data 函数来定义组件自身的状态
    data () {
        return {
            foo: 'hello world'
        }
    },

    // 组件的渲染函数， 其返回值必须是一个虚拟DOM
    render () {
        return {
            type: 'div',
            children: `foo is ${this.foo}`,//在渲染函数内使用组件状态
        }
    }
}

const CompVNode = {
    type: MyComponent,
    props: {
        title: 'A big Title',
        otther: this.val
    }
}

renderer.render(CompVNode, document.querySelector('#app'))

